<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="QEycl" id="QEycl"><span data-lake-id="u9ef42371" id="u9ef42371">典型回答</span></h1>
  <p data-lake-id="u8de2ac1b" id="u8de2ac1b"><br></p>
  <p data-lake-id="u29da3896" id="u29da3896"><span data-lake-id="ub176d982" id="ub176d982">解决接口幂等问题，只需要记住一句口令"</span><strong><span data-lake-id="u8924caca" id="u8924caca">一锁、二判、三更新</span></strong><span data-lake-id="u3d64ef9d" id="u3d64ef9d">"，只要严格遵守这个过程，那么就可以解决并发问题。</span></p>
  <p data-lake-id="ud84130c9" id="ud84130c9"><span data-lake-id="u0eb240d1" id="u0eb240d1">​</span><br></p>
  <p data-lake-id="u7ea73203" id="u7ea73203"><strong><span data-lake-id="u2c58a42b" id="u2c58a42b">一锁：第一步，先加锁。可以加分布式锁、或者悲观锁都可以。但是一定要是一个互斥锁！</span></strong></p>
  <p data-lake-id="u29b52857" id="u29b52857"><strong><span data-lake-id="uab302093" id="uab302093">二判：第二步，进行幂等性判断。可以基于状态机、流水表、唯一性索引等等进行重复操作的判断。</span></strong></p>
  <p data-lake-id="u9a4910d0" id="u9a4910d0"><strong><span data-lake-id="u8efff2b8" id="u8efff2b8">三更新：第三步，进行数据的更新，将数据进行持久化。</span></strong></p>
  <p data-lake-id="u2e6e3fcb" id="u2e6e3fcb"><span data-lake-id="uaa3cc0af" id="uaa3cc0af">​</span><br></p>
  <p data-lake-id="u87223109" id="u87223109"><span data-lake-id="u8dc84d82" id="u8dc84d82">三步需要严格控制顺序，确保加锁成功后进行数据查询和判断，幂等性判断通过后再更新，更新结束后释放锁。</span></p>
  <p data-lake-id="u21e23e1f" id="u21e23e1f"><span data-lake-id="u6bd04267" id="u6bd04267">​</span><br></p>
  <p data-lake-id="u818a45ea" id="u818a45ea"><span data-lake-id="u0f8458e9" id="u0f8458e9">以上操作需要有一个前提，那就是第一步加锁、和第二步判断的时候，需要有一个依据，这个就是幂等号了，通常需要和上游约定一个唯一ID作为幂等号。然后通过对幂等号加锁，再通过幂等号进行幂等判断即可。</span></p>
  <p data-lake-id="uf58e0901" id="uf58e0901"><span data-lake-id="u74459670" id="u74459670">​</span><br></p>
  <p data-lake-id="u40142c64" id="u40142c64"><span data-lake-id="uc35120c6" id="uc35120c6">一锁这个过程，建议使用Redis实现分布式锁，因为他是非阻塞的高效率的互斥锁。非常适合在幂等控制场景中。</span></p>
  <p data-lake-id="u5c8cd713" id="u5c8cd713"><span data-lake-id="u4df738b0" id="u4df738b0">​</span><br></p>
  <p data-lake-id="ua9128737" id="ua9128737"><span data-lake-id="u946ef167" id="u946ef167">二判这个过程，如果有操作流水，建议基于操作流水做幂等，并将幂等号作为唯一性约束，确保唯一性。如果没有流水，那么基于状态机也是可以的。</span></p>
  <p data-lake-id="u6cba94c8" id="u6cba94c8"><span data-lake-id="uc6650551" id="uc6650551">​</span><br></p>
  <p data-lake-id="u3fb4e2f8" id="u3fb4e2f8"><span data-lake-id="ub3648a31" id="ub3648a31">但是不管怎么样，数据库的唯一性约束都要加好，这是系统的最后一道防线。万一前面的锁失效了，这里也能控制得住不会产生脏数据。</span></p>
  <p data-lake-id="u576737be" id="u576737be"><br></p>
  <h1 data-lake-id="ega7C" id="ega7C"><span data-lake-id="u9d0a7d82" id="u9d0a7d82">扩展知识</span></h1>
  <p data-lake-id="u434dbe16" id="u434dbe16"><br></p>
  <h2 data-lake-id="f3jPu" id="f3jPu"><span data-lake-id="u75d689a8" id="u75d689a8">请求幂等与业务幂等</span></h2>
  <p data-lake-id="uecb777c8" id="uecb777c8"><br></p>
  <p data-lake-id="u7819058a" id="u7819058a"><strong><span data-lake-id="ud76dea08" id="ud76dea08">请求幂等</span></strong><span data-lake-id="u9ffc075a" id="u9ffc075a">：每次请求，如果参数一样，结果也要一样。</span></p>
  <p data-lake-id="u0bd43269" id="u0bd43269"><span data-lake-id="u3eccd197" id="u3eccd197">​</span><br></p>
  <p data-lake-id="uad3ad25f" id="uad3ad25f"><strong><span data-lake-id="u01d2735c" id="u01d2735c">业务幂等</span></strong><span data-lake-id="u00184d06" id="u00184d06">：同一次业务请求，再拿到最终状态之后的每次请求，结果要保证一样。再没拿到最终状态之前，每一次请求需要正常执行业务逻辑，直到推进到最终状态。</span></p>
  <p data-lake-id="u024f4d72" id="u024f4d72"><span data-lake-id="u5d97d440" id="u5d97d440">​</span><br></p>
  <p data-lake-id="uac52d487" id="uac52d487"><span data-lake-id="u5a721e44" id="u5a721e44">比如，一次支付请求，如果支付返回处理中，或者系统异常等，我们需要重试，继续调用，直到他明确的返回支付成功，或者明确的无法成功的支付失败结果。</span></p>
  <p data-lake-id="ub3cbab74" id="ub3cbab74"><span data-lake-id="uc47640b5" id="uc47640b5">​</span><br></p>
  <p data-lake-id="ua8904db7" id="ua8904db7"><span data-lake-id="u8fb707e6" id="u8fb707e6">一般来说，在幂等请求中，应该如下返回：</span></p>
  <p data-lake-id="u80bc507e" id="u80bc507e"><span data-lake-id="u8304ad78" id="u8304ad78">​</span><br></p>
  <pre lang="java"><code>
success = true
responseCode = DUPLICATED
</code></pre>
  <p data-lake-id="ud20aac62" id="ud20aac62"><span data-lake-id="u649ae30b" id="u649ae30b">​</span><br></p>
  <p data-lake-id="u370b9542" id="u370b9542"><span data-lake-id="uf4149787" id="uf4149787">这样既能让别人知道是成功了，也能知道是因为幂等而导致的成功</span></p>
  <h2 data-lake-id="amDJV" id="amDJV"><span data-lake-id="u4a417669" id="u4a417669">示例</span></h2>
  <p data-lake-id="u8d7ff3e9" id="u8d7ff3e9"><br></p>
  <p data-lake-id="ue25a10f3" id="ue25a10f3"><span data-lake-id="u7bbcb132" id="u7bbcb132">以下是个简单的示例：</span></p>
  <p data-lake-id="ue07b144a" id="ue07b144a"><span data-lake-id="u8f9bc01a" id="u8f9bc01a">​</span><br></p>
  <pre lang="java"><code>
//一锁：先加一个分布式锁
@DistributeLock(scene = "OEDER", keyExpression = "#request.identifier", expire = 3000)
public OrderResponse apply(OrderRequest request) {
    OrderResponse response = new OrderResponse();
  	//二判：判断请求是否执行成功过
    OrderDTO orderDTO = orderService.queryOrder(request.getProduct(), request.getIdentifier());
    if (orderDTO != null) {
        response.setSuccess(false);
        response.setResponseCode("DUPLICATED");
        return response;
    }

		//三更新：执行更新的业务逻辑
  	return orderService.order(request);
}
</code></pre>
 </body>
</html>